﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Linq;

using EmbeddedSensorCloud.HTTP;
using EmbeddedSensorCloud.Plugin;
using EmbeddedSensorCloud.Core.Plugin;

namespace EmbeddedSensorCloud.Core.Server
{
    public sealed class ServerCore
    {
        #region Events

        public delegate void ServerStatusChangedEventHandler(EventArgs serverState);
        public event ServerStatusChangedEventHandler ServerStatusChanged;

        #endregion

        #region Properties

        private TcpListener listener;
        private Action<NetworkStream, Request> newClientAction;

        public bool Listening
        {
            get
            {
                return this.listener != null;
            }
        }

        public IPAddress IP
        {
            get;
            private set;
        }

        public int Port
        {
            get;
            private set;
        }

        #endregion

        public ServerCore(Action<NetworkStream, Request> newClientAction)
        {
            this.newClientAction = newClientAction;
        }

        private void DoAcceptTcpClient(IAsyncResult ar)
        {
            if (ar == null || ar.AsyncState == null)
            {
                throw new ArgumentNullException();
            }

            TcpListener listener = (TcpListener)ar.AsyncState;
            TcpClient tcpClient = null;

            try
            {
                tcpClient = listener.EndAcceptTcpClient(ar);
                this.listener.BeginAcceptTcpClient(DoAcceptTcpClient, listener);
            }
            catch (Exception)
            {
                return;
            }

            ChangeStatus(new ClientOpenedEventArgs(tcpClient.Client.RemoteEndPoint));

            if (tcpClient.Client.Connected)
            {
                Request req = null;
                try
                {
                    req = Request.Parse(tcpClient.GetStream());
                }
                catch (Exception e)
                {
                    ChangeStatus(new RequestParsingFailedEventArgs(e.ToString()));
                    return;
                }

                ChangeStatus(new RequestParsedEventArgs(req.Url));
                if (this.newClientAction != null)
                {
                    this.newClientAction(tcpClient.GetStream(), req);
                }
            }

            ChangeStatus(new ClientClosedEventArgs(tcpClient.Client.RemoteEndPoint));
            tcpClient.Close();
        }

        public void Start(IPAddress ip, int port)
        {
            if (ip == null)
            {
                throw new ArgumentNullException("IP cannot be null");
            }

            if (ip == IPAddress.None || ip == IPAddress.IPv6None)
            {
                throw new InvalidIPException(IPAddress.None.ToString(), "IP cannot be none");
            }

            if (port <= 0 || port >= 65536)
            {
                throw new ArgumentOutOfRangeException("Port must be between [1, 65535]");
            }

            if (PortHelper.IsUsed(port))
            {
                throw new PortAlreadyInUseException(port, "Port already in use");
            }

            if (this.listener != null)
            {
                var endpoint = (IPEndPoint)this.listener.LocalEndpoint;
                throw new ServerAlreadyListeningException(endpoint.Address, endpoint.Port);
            }

            this.listener = new TcpListener(ip, port);

            try
            {
                listener.Start();
                listener.BeginAcceptTcpClient(DoAcceptTcpClient, listener);
            }
            catch (Exception e)
            {
                throw new ServerException("Starting server failed", e);
            }

            this.IP = ip;
            this.Port = port;
        }

        public void Stop()
        {
            if (this.listener == null)
            {
                throw new ServerNotListeningException("Server is not listening");
            }

            try
            {
                this.listener.Stop();
            }
            catch (SocketException e)
            {
                throw new ServerException("Stopping server failed", e);
            }

            this.listener = null;
        }
            
        private void ChangeStatus(EventArgs serverState)
        {
            if (this.ServerStatusChanged != null)
            {
                this.ServerStatusChanged(serverState);
            }
        }
    }
}

